package com.sl.plugin.step.net;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import com.bazaarvoice.jolt.Chainr;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Maps;
import com.sl.core.domain.dto.JsonMappingFieldDefine;
import com.sl.core.utils.JsonUtil;
import com.sl.core.engine.meta.impl.AbstractProcessCompDescriptor;
import com.sl.core.engine.rest.RestTemplateManager;
import com.sl.core.engine.step.ProcessCompService;
import com.sl.core.engine.step.impl.AbstractProcessComp;
import com.sl.core.engine.transfer.MappingFiledDefineToJoltDslTransfer;
import com.sl.core.engine.utils.BeanUtil;
import lombok.SneakyThrows;
import org.apache.commons.compress.utils.Lists;
import org.noear.snack.ONode;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 *
 */
public class NetHttpComp extends AbstractProcessComp<Map<String, Object>, Map<String, Object>> implements ProcessCompService {


    public NetHttpComp(AbstractProcessCompDescriptor abstractProcessCompDescriptor) {
        super(abstractProcessCompDescriptor);
    }

    @SneakyThrows
    @Override
    public Map<String, Object> input(Map<String, Object> param) {

        NetHttpCompDescriptor descriptor = getDescriptor();
        String url = descriptor.getStrAttr("config.url");
        Assert.isTrue(StrUtil.isNotBlank(url), "请配置url");

        String method = descriptor.getStrAttr("config.method");
        Assert.isTrue(StrUtil.isNotBlank(method), "请配置Method");

        Map<String, Object> queryParamConfigMap = descriptor.getMapAttr("config.queryParamConfig");
        ONode oNode = ONode.loadObj(param);

        if (MapUtil.isNotEmpty(queryParamConfigMap)) {
            Map<String, Object> map = buildQueryParamConfigMap(queryParamConfigMap);
            String s = buildQueryParamConfig(map, oNode);
            url = appendUrlParam(url, s);
        }

        Map<String, Object> headerConfig = descriptor.getMapAttr("config.headerConfig");
        Map<String, Object> bodyConfig = descriptor.getMapAttr("config.bodyConfig");
        HttpRequest httpClient = RestTemplateManager.INSTANCE.getHttpClient(url);
        httpClient.setMethod(Method.valueOf(method));

        if (MapUtil.isNotEmpty(headerConfig)) {

            Map<String, Object> map = buildHeaderMap(headerConfig);

            List<Pair<String, String>> pairs = headerInject(map, oNode);
            if (CollUtil.isNotEmpty(pairs)) {
                for (Pair<String, String> pair : pairs) {
                    httpClient.header(pair.getKey(), pair.getValue());
                }
            }
        }
        if (MapUtil.isNotEmpty(bodyConfig)) {
            setBody(httpClient, bodyConfig, oNode);
        }
        HttpResponse execute = httpClient.execute();

        responseSuccess(execute);

//        System.out.println(execute.body());
        String body = execute.body();
        Map<String, Object> result = Maps.newHashMap();
        if (JsonUtil.isJsonStr(body)) {
            result.put("response", JSONUtil.parse(body));
        } else {
            result.put("response", body);
        }
        //判断响应值是不是json,是json转json
        Map<String, List<String>> headers = execute.headers();
        result.put("headers", headerTo(headers));
        return result;
    }

    private void responseSuccess(HttpResponse execute) {
        int status = execute.getStatus();
        Assert.isTrue(status == 200, execute.body());
    }

    private Map<String, String> headerTo(Map<String, List<String>> headers) {
        Map<String, String> result = Maps.newHashMap();
        for (Map.Entry<String, List<String>> stringListEntry : headers.entrySet()) {
            String key = stringListEntry.getKey();
            if (ObjectUtil.isNull(key)) {
                key = "";
            }
            result.put(key, String.join(",", stringListEntry.getValue()));
        }
        return result;
    }

    private Boolean responseIsJson(String body) {
        //content-type判断
        return JsonUtil.isJsonStr(body);
    }

    private void setBody(HttpRequest httpClient, Map<String, Object> bodyConfig, ONode oNode) {
        String viewTypeCode = MapUtil.getStr(bodyConfig, "viewTypeCode");
        String fileJsonPath = MapUtil.getStr(bodyConfig, "file");

        //文件
        //todo 需要再次考虑下
        if (StrUtil.equals(viewTypeCode, "file")) {
            Object object = oNode.select(fileJsonPath).toObject();
            httpClient.body(object.toString(), ContentType.MULTIPART.getValue());
        } else if (StrUtil.equalsAnyIgnoreCase(viewTypeCode, "body", "form")) {
            NetHttpCompDescriptor descriptor = getDescriptor();
            Map<String, Object> maps = MapUtil.get(bodyConfig, "body", Map.class);

            List<Map<String, Object>> params = descriptor.getListMapAttr(maps, "params");
            //找到对象关系
            List<JsonMappingFieldDefine> jsonMappingFieldDefine = JsonUtil.toBean(params, new TypeReference<List<JsonMappingFieldDefine>>() {
            });

            if (CollUtil.isNotEmpty(jsonMappingFieldDefine)) {
                //转移到了映射
                String transfer = MappingFiledDefineToJoltDslTransfer.transfer(jsonMappingFieldDefine);

                List chainrSpecJSON = JsonUtil.toList(transfer, Map.class);
                Chainr chainr = Chainr.fromSpec(chainrSpecJSON);
                Object transformedOutput = chainr.transform(oNode.toData());
                Map<String, Object> paramMap = BeanUtil.convert(transformedOutput);

                if (StrUtil.equalsAnyIgnoreCase(viewTypeCode, "body")) {
                    httpClient.body(JsonUtil.toStr(paramMap));
                } else {
                    httpClient.form(paramMap);
                }
            }
        }
    }

    private List<Pair<String, String>> headerInject(Map<String, Object> headerConfig, ONode oNode) {
        List<Map<String, Object>> maps = MapUtil.get(headerConfig, "struct", List.class);
        if (CollUtil.isEmpty(maps)) {
            return Collections.emptyList();
        }
        List<Pair<String, String>> result = maps.stream().map(v -> {
            String str = MapUtil.getStr(v, "fieldCode");
            String rightValue = MapUtil.getStr(v, "rightValue");
            if (StrUtil.contains(rightValue, "$")) {
                rightValue = jsonPathToString(oNode.select(rightValue).toObject());
            }
            return new Pair<String, String>(str, rightValue);
            //  return str + "=" + rightValue;
        }).toList();
        return result;
    }

    private Map<String, Object> buildHeaderMap(Map<String, Object> queryParamConfigMap) {

        String viewTypeCode = MapUtil.getStr(queryParamConfigMap, "viewTypeCode");
        if (!StrUtil.equals(viewTypeCode, "raw")) {
            return queryParamConfigMap;
        }

        List<Map<String, Object>> resList = Lists.newArrayList();
        String result = MapUtil.getStr(queryParamConfigMap, "raw");
        String[] split = result.split(";");

        for (String sp : split) {
            String[] splitEq = sp.split("=");
            Map<String, Object> res = Maps.newHashMap();
            res.put("fieldCode", ArrayUtil.get(splitEq, 0));
            res.put("rightValue", ArrayUtil.get(splitEq, 1));
            resList.add(res);
        }
        queryParamConfigMap.put("struct", resList);
        return queryParamConfigMap;
    }

    private Map<String, Object> buildQueryParamConfigMap(Map<String, Object> queryParamConfigMap) {

        String viewTypeCode = MapUtil.getStr(queryParamConfigMap, "viewTypeCode");
        if (!StrUtil.equals(viewTypeCode, "raw")) {
            return queryParamConfigMap;
        }

        List<Map<String, Object>> resList = Lists.newArrayList();
        String result = MapUtil.getStr(queryParamConfigMap, "raw");
        String[] split = result.split("&");

        for (String sp : split) {
            String[] splitEq = sp.split("=");
            Map<String, Object> res = Maps.newHashMap();
            res.put("fieldCode", ArrayUtil.get(splitEq, 0));
            res.put("rightValue", ArrayUtil.get(splitEq, 1));
            resList.add(res);
        }
        queryParamConfigMap.put("struct", resList);
        return queryParamConfigMap;
    }

    private String appendUrlParam(String url, String param) {

        String finalUrl = url;
        if (StrUtil.indexOf(url, '?') != -1) {
            finalUrl += "&" + param;
        } else {
            finalUrl += "?" + param;
        }
        return finalUrl;
    }


    private String buildQueryParamConfig(Map<String, Object> queryParamConfigMap, ONode oNode) {
        String result;
        List<Map<String, Object>> maps = MapUtil.get(queryParamConfigMap, "struct", List.class);
        if (CollUtil.isEmpty(maps)) {
            return StrUtil.EMPTY;
        }
        result = maps.stream().map(v -> {
            String str = MapUtil.getStr(v, "fieldCode");
            String rightValue = MapUtil.getStr(v, "rightValue");
            if (StrUtil.contains(rightValue, "$")) {
                rightValue = jsonPathToString(oNode.select(rightValue).toObject());
            }
            return str + "=" + rightValue;
        }).collect(Collectors.joining("&"));


        return result;
    }


    public String jsonPathToString(Object obj) {

        if (ObjectUtil.isNull(obj)) {
            return StrUtil.EMPTY;
        }
        if (obj instanceof Collection<?> objList) {
            return objList.stream().map(JsonUtil::toStr).collect(Collectors.joining(","));
        } else {
            return JsonUtil.toStr(obj);
        }
    }
}
